package net.joint_painting.client.view;

import gwt.g2d.client.graphics.Color;
import gwt.g2d.client.graphics.KnownColor;
import gwt.g2d.client.graphics.LineCap;
import gwt.g2d.client.graphics.LineJoin;
import gwt.g2d.client.graphics.Surface;
import gwt.g2d.client.graphics.shapes.ShapeBuilder;
import gwt.g2d.client.math.Vector2;
import net.joint_painting.client.model.Model;
import net.joint_painting.client.observer.ModelEvent;
import net.joint_painting.client.observer.ModelListener;
import net.joint_painting.client.observer.ViewObject;
import net.joint_painting.client.observer.ViewObserverListener;
import net.joint_painting.shared.model.Line;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * @author CAM
 * @version $Revision: 1.0 $
 */
public class Space extends Composite implements KeyDownHandler,
	MouseDownHandler, MouseMoveHandler, MouseUpHandler, ModelListener {

    /**
     * @author CAM
     * @version $Revision: 1.0 $
     */
    interface SpaceUiBinder extends UiBinder<Widget, Space> {
    }

    private static SpaceUiBinder uiBinder = GWT.create(SpaceUiBinder.class);

    @UiField
    SimplePanel PaintSurfacePanel;

    private ViewObserverListener viewObserverListener;

    private Vector2 lastPosition = new Vector2();
    private boolean isLeftButtonDown, isRightButtonDown;
    private Surface paintSurface;
    private Color painterColor = KnownColor.BLACK;
    private Color deleteColor = KnownColor.WHITE;
    private int width = com.google.gwt.user.client.Window.getClientWidth();
    private int height = com.google.gwt.user.client.Window.getClientHeight();
    private Model model;

    public Space() {
	initWidget(uiBinder.createAndBindUi(this));
	initPaintSurface();
    }

    /**
     * Method addViewObserver.
     * 
     * @param viewObserverListener
     *            ViewObserverListener
     */
    public void addViewObserver(ViewObserverListener viewObserverListener) {
	this.viewObserverListener = viewObserverListener;
    }

    /**
     * Method drawLine.
     * 
     * @param line
     *            Line
     * @param surface
     *            Surface
     */
    private void drawLine(Line line, Surface surface) {
	surface.setStrokeStyle(line.getColor()).setLineWidth(line.getSize());
	surface.save().setLineCap(LineCap.ROUND).setLineJoin(LineJoin.ROUND);
	surface.strokeShape(new ShapeBuilder().drawLineSegment(
		new Vector2(line.getX1(), line.getY1()),
		new Vector2(line.getX2(), line.getY2())).build());
	surface.restore();
    }

    /**
     * Method drawSurface.
     * 
     * @param surface
     *            Surface
     * @param x
     *            Integer
     * @param y
     *            Integer
     */
    private void drawSurface(Surface surface, Integer x, Integer y) {
	Integer size = net.joint_painting.shared.logic.Surface.size;
	if (surface == null)
	    surface = new Surface(size, size);

	for (Line line : model.getLines(x, y)) {
	    if (line != null) {
		drawLine(line, surface);
	    }
	}

	paintSurface.putImageData(surface.getImageData(0, 0, size, size),
		net.joint_painting.shared.logic.Surface.getSurfacePositionX(x)
			- model.getPositionX() / model.getZoomLevel(),
		net.joint_painting.shared.logic.Surface.getSurfacePositionY(y)
			- model.getPositionY() / model.getZoomLevel());
	paintSurface.restore();
    }

    private void initPaintSurface() {
	paintSurface = new Surface(width, height);
	paintSurface.addMouseDownHandler(this);
	paintSurface.addMouseMoveHandler(this);
	paintSurface.addMouseUpHandler(this);
	paintSurface.addKeyDownHandler(this);
	this.PaintSurfacePanel.clear();
	this.PaintSurfacePanel.add(paintSurface);
    }

    /**
     * Method onKeyDown.
     * 
     * @param event
     *            KeyDownEvent
     * 
     * @see com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(KeyDownEvent)
     */
    @Override
    public void onKeyDown(KeyDownEvent event) {
	com.google.gwt.user.client.Window.alert("event");
	if (event.isUpArrow()) {
	    com.google.gwt.user.client.Window.alert("up");
	    viewObserverListener.notify(new ViewObject(this,
		    ViewObject.ViewObjectType.MOVEUP));
	} else if (event.isDownArrow())
	    viewObserverListener.notify(new ViewObject(this,
		    ViewObject.ViewObjectType.MOVEDOWN));
	else if (event.isLeftArrow())
	    viewObserverListener.notify(new ViewObject(this,
		    ViewObject.ViewObjectType.MOVELEFT));
	else if (event.isRightArrow())
	    viewObserverListener.notify(new ViewObject(this,
		    ViewObject.ViewObjectType.MOVERIGHT));
    }

    /**
     * Method onModelUpdate.
     * 
     * @param event
     *            ModelEvent
     * 
     * @see net.joint_painting.client.observer.ModelListener#onModelUpdate(ModelEvent)
     */
    @Override
    public void onModelUpdate(ModelEvent event) {
	if (event == null)
	    return;
	switch (event.getType()) {
	case UPDATE:
	    Surface surface = model.getSurface((Integer) event.getArgument1(),
		    (Integer) event.getArgument2());
	    drawSurface(surface, (Integer) event.getArgument1(),
		    (Integer) event.getArgument2());
	    break;
	case MOVE:
	    repaint();
	    break;
	case ZOOM:
	    repaint();
	    break;
	default:
	    break;
	}
    }

    /**
     * Method onMouseDown.
     * 
     * @param event
     *            MouseDownEvent
     * 
     * @see com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(MouseDownEvent)
     */
    @Override
    public void onMouseDown(MouseDownEvent event) {
	switch (event.getNativeButton()) {
	case NativeEvent.BUTTON_LEFT:
	    isLeftButtonDown = true;
	    break;
	case NativeEvent.BUTTON_RIGHT:
	    painterColor = model.getPainterColor();
	    viewObserverListener.notify(new ViewObject(this,
		    ViewObject.ViewObjectType.SETPAINTERCOLOR, deleteColor));
	    viewObserverListener.notify(new ViewObject(this,
		    ViewObject.ViewObjectType.SETPAINTERSIZE, model
			    .getPainterSize() * 2));
	    isRightButtonDown = true;
	    break;
	}
	lastPosition.set(
		event.getX() * model.getZoomLevel() + model.getPositionX(),
		event.getY() * model.getZoomLevel() + model.getPositionY());
    }

    /**
     * Method onMouseMove.
     * 
     * @param event
     *            MouseMoveEvent
     * 
     * @see com.google.gwt.event.dom.client.MouseMoveHandler#onMouseMove(MouseMoveEvent)
     */
    @Override
    public void onMouseMove(MouseMoveEvent event) {
	if (event.getSource() == paintSurface) {
	    if (isLeftButtonDown || isRightButtonDown) {

		viewObserverListener.notify(new ViewObject(this,
			ViewObject.ViewObjectType.DRAWLINE, new Vector2(event
				.getX()
				* model.getZoomLevel()
				+ model.getPositionX(), event.getY()
				* model.getZoomLevel() + model.getPositionY()),
			lastPosition));
		lastPosition.set(
			event.getX() * model.getZoomLevel()
				+ model.getPositionX(),
			event.getY() * model.getZoomLevel()
				+ model.getPositionY());
	    }
	}
    }

    /**
     * Method onMouseUp.
     * 
     * @param event
     *            MouseUpEvent
     * 
     * @see com.google.gwt.event.dom.client.MouseUpHandler#onMouseUp(MouseUpEvent)
     */
    @Override
    public void onMouseUp(MouseUpEvent event) {
	switch (event.getNativeButton()) {
	case NativeEvent.BUTTON_LEFT:
	    isLeftButtonDown = false;
	    break;
	case NativeEvent.BUTTON_RIGHT:
	    viewObserverListener.notify(new ViewObject(this,
		    ViewObject.ViewObjectType.SETPAINTERCOLOR, painterColor));
	    viewObserverListener.notify(new ViewObject(this,
		    ViewObject.ViewObjectType.SETPAINTERSIZE, model
			    .getPainterSize() / 2));
	    isRightButtonDown = false;
	    break;
	}
    }

    public void repaint() {
	paintSurface.clear();
	for (int x = model.getPositionX(); x <= model.getPositionX() + width
		* model.getZoomLevel(); x += net.joint_painting.shared.logic.Surface.size) {
	    for (int y = model.getPositionY(); y <= model.getPositionY()
		    + height * model.getZoomLevel(); y += net.joint_painting.shared.logic.Surface.size) {
		Surface surface = model.getSurface(x, y);
		drawSurface(surface, x, y);
	    }
	}
    }

    /**
     * Method setModel.
     * 
     * @param model
     *            Model
     */
    public void setModel(Model model) {
	this.model = model;
	model.attach(this);
    }

    /**
     * Method setSize.
     * 
     * @param width
     *            Integer
     * @param height
     *            Integer
     */
    public void setSize(Integer width, Integer height) {
	this.width = width;
	this.height = height;
	paintSurface.setSize(width, height);
	repaint();
    }
}
